//********************************************************************************************************************//
//ORGANIZATION:
//AUTHOR: bpeng(bogers.peng@gmail.com)
//SUMMARY:
//********************************************************************************************************************//
#ifndef NATIVE_SYSTEM_BASE_MAP_H
#define NATIVE_SYSTEM_BASE_MAP_H

#ifndef NATIVE_SYSTEM_BASE_COLLECTION_H
#include "System/Base/Collection.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_FILTER_H
#include "System/Base/Filter.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_MAPCURSOR_H
#include "System/Base/MapCursor.h"
#endif
#ifndef NATIVE_SYSTEM_BASE_DUAD_H
#include "System/Base/Duad.h"
#endif

namespace Native { namespace System
{
    //****************************************************************************************************************//
    //CInexistentKeyException
    //
    //****************************************************************************************************************//
    template<
        typename _TKey,
        typename _FKeyToString = function<wstring(const _TKey&)>
    >
    class NATIVE_LIBRARY_EXPORT CInexistentKeyException :
        public CException
    {
        NATIVE_DECLARE_OBJECT;

        NATIVE_REDEFINE(public, _TKey, TKey, T_KEY);
        typedef _FKeyToString FKeyToString;
        NATIVE_REDEFINE(public, CInexistentKeyException<TKey COMMA FKeyToString>, Class, CLASS);

    public:
        static void Check(
            C_SCENE& scene,
            const ICursorial* cursor,
            T_KEY& key,
            FKeyToString convert_key_to_string,
            WSTRING& detail = EMPTY_STRING,
            WSTRING& arguments = EMPTY_STRING) throw()
        {
            if (!cursor->Exist()) {
                throw CInexistentKeyException(location, key, convert_key_to_string, detail, arguments);
            }
        }

    protected:
        CInexistentKeyException()
        {};
    public:
        CInexistentKeyException(CLASS& that) :
            CException(that)
        {};

        CInexistentKeyException(CLASS&& that) :
            CException(that)
        {};

        CInexistentKeyException(
            C_SCENE& scene,
            T_KEY& key,
            FKeyToString convert_key_to_string,
            WSTRING& detail = EMPTY_STRING,
            WSTRING& arguments = EMPTY_STRING)
        {
            wchar_t message[MAX_MESSAGE_LENGTH] = {0};

            wsprintf(message, L"Key:[%s] does not exist.", convert_key_to_string(key).c_str());

            Append(CCase(scene, message, detail, arguments));
        }
    };
    //****************************************************************************************************************//
    //CDuplicateKeyException
    //
    //****************************************************************************************************************//
    template<
        typename _TKey,
        typename _FKeyToString = function<wstring(const _TKey&)>
    >
    class NATIVE_LIBRARY_EXPORT CDuplicateKeyException :
        public CException
    {
        NATIVE_DECLARE_OBJECT;

        NATIVE_REDEFINE(public, _TKey, TKey, T_KEY);
        typedef _FKeyToString FKeyToString;
        NATIVE_REDEFINE(public, CDuplicateKeyException<TKey COMMA FKeyToString>, Class, CLASS);

    public:
        static void Check(
            C_SCENE& scene,
            const ICursorial* cursor,
            T_KEY& key,
            FKeyToString convert_key_to_string,
            WSTRING& detail = EMPTY_STRING,
            WSTRING& arguments = EMPTY_STRING)
        {
            if (cursor->Exist()) {
                throw CDuplicateKeyException(scene, key, convert_key_to_string, detail, arguments);
            }
        }

    protected:
        CDuplicateKeyException()
        {};
    public:
        CDuplicateKeyException(CLASS& that) :
            CException(that)
        {};

        CDuplicateKeyException(CLASS&& that) :
            CException(that)
        {};

        CDuplicateKeyException(
            C_SCENE& scene,
            T_KEY& key,
            FKeyToString convert_key_to_string,
            WSTRING& detail = EMPTY_STRING,
            WSTRING& arguments = EMPTY_STRING)
        {
            wchar_t message[MAX_MESSAGE_LENGTH] = {0};

            wsprintf(message, L"Key:[%s] is duplicate.", convert_key_to_string(key).c_str());

            Append(CCase(scene, message, detail, arguments));
        }
    };
    //****************************************************************************************************************//
    //CMap
    //
    //****************************************************************************************************************//
    template<
        typename _TKey,
        typename _TValue,
        typename _FKeyToString = function<wstring(const _TKey&)>
    >
    class NATIVE_LIBRARY_EXPORT CMap :
        public CCollection,
        public IFilter<CDuad<_TKey, _TValue>>
    {
        NATIVE_DECLARE_OBJECT;

        NATIVE_REDEFINE(public, _TKey, TKey, T_KEY);
        NATIVE_REDEFINE(public, _TValue, TValue, T_VALUE);
        NATIVE_REDEFINE(public, CMap<TKey COMMA TValue>, Class, CLASS);
        typedef _FKeyToString FKeyToString;
        
        NATIVE_DECLARE_PROPERTY(public, public, bool, Overlap, _overlap);

    protected:
        map<TKey, TValue> _data;
        FKeyToString _key_to_string;

        CMapCursor<TKey, TValue> _Find(T_KEY& key) const
        {
            if (_data.empty()) {
                return CMapCursor<TKey, TValue>();
            } else {
                auto found = _data.find(key);

                return (found != _data.end())?
                    CMapCursor<TKey, TValue>(found) :
                    CMapCursor<TKey, TValue>();
            }
        };

    public:
        CMap() :
            _overlap(true),
            _key_to_string(nullptr)
        {};

        CMap(FKeyToString key_to_string) :
            _overlap(true),
            _key_to_string(key_to_string)
        {};

        CMap(CLASS& that) :
            CCollection(that),
            _data(that._data),
            _overlap(that._overlap),
            _key_to_string(that._key_to_string)
        {};

        CMap(CLASS&& that) :
            CCollection(that),
            _data(that._data),
            _overlap(that._overlap),
            _key_to_string(that._key_to_string)
        {};

        CMap(const map<TKey, TValue>& map, FKeyToString key_to_string, bool overlap = true) :
            _data(map),
            _overlap(overlap),
            _key_to_string(key_to_string)
        {};

        virtual ~CMap()
        {};
        //{CCollection
        virtual size_t Count() const
        {
            return _data.size();
        };

        virtual bool Empty() const
        {
            return _data.empty();
        };

        virtual void Clear()
        {
            _data.clear();
        };
        //}
        //{IFilter
        vector<CDuad<TKey, TValue>> Select(function<bool(const CDuad<TKey, TValue>&)> have) const
        {
            vector<CDuad<TKey, TValue>> result;

            for (auto each : _data) {
                CDuad<TKey, TValue> duad(each);

                if (have(duad)) {
                    result.push_back(duad);
                }
            }

            return move(result);
        }
        //}
        void KeyToString(FKeyToString functor)
        {
            _key_to_string = functor;
        };

        CMapCursor<TKey, TValue> Find(T_KEY& key) const
        {
            return _Find(key);
        };

        CMapCursor<TKey, TValue> Find(T_KEY& key)
        {
            return _Find(key);
        };
        
        CMapCursor<TKey, TValue> LocateFirst() const
        {
            return _data.empty()?
                CMapCursor<TKey, TValue>() :
                CMapCursor<TKey, TValue>(_data.begin());
        };

        CMapCursor<TKey, TValue> LocateLast() const
        {
            return _data.empty()?
                CMapCursor<TKey, TValue>() : CMapCursor<TKey, TValue>(--_data.end());
        }

        bool AtBeginning(T_KEY& key) const
        {
            if (_data.empty()) {
                return false;
            } else {
                auto found = _data.find(key);

                return found == _data.end()?
                    false : found == _data.begin();
            }
        };

        bool AtEnding(T_KEY& key) const
        {
            if (_data.empty()) {
                return false;
            } else {
                auto found = _data.find(key);

                if (found == _data.end()) {
                    return false;
                } else {
                    auto end = --_data.end();

                    return found == end;
                }
            }
        }

        void Add(T_KEY& key, T_VALUE& value) throw()
        {
            auto cursor = Find(key);

            if (cursor.Exist()) {
                if (Overlap()) {
                    _data[cursor.Key()] = value;
                } else {
                    if(_key_to_string == nullptr) {
                    }
                    throw CDuplicateKeyException<TKey, FKeyToString>(
                        NATIVE_SCENE(L"Add"),
                        key,
                        _key_to_string,
                        L"key");
                }
            } else {
                _data.insert(pair<TKey, TValue>(key, value));
            }
        };

        void Add(CDuad<TKey, TValue>& data) throw()
        {
            try {
                Add(data.Left(), data.Right());
            } catch(CException& e) {
                NATIVE_THROW_CAUGHT(L"Add", e);
            }
        };
        
        TValue& Value(const CMapCursor<TKey, TValue>& cursor)
        {
            return Value(cursor.Key());
        }

        TValue Value(const CMapCursor<TKey, TValue>& cursor) const
        {
            return Value(cursor.Key());
        }

        TValue& Value(T_KEY& key) throw()
        {
            auto cursor = Find(key);

            if (cursor.Exist()) {
                return _data.at(key);
            } else {
                throw CInexistentKeyException<TKey, FKeyToString>(
                    NATIVE_SCENE(L"Value"),
                    key,
                    _key_to_string,
                    L"key");
            }
        };

        TValue Value(T_KEY& key) const throw()
        {
            auto cursor = Find(key);

            if (cursor.Exist()) {
                return _data.at(key);
            } else {
                throw CInexistentKeyException<TKey, FKeyToString>(
                    NATIVE_SCENE(L"Value"),
                    key,
                    _key_to_string,
                    L"key");
            }
        };

        CLASS& operator=(CLASS& rvalue)
        {
            _data = rvalue._data;
            _overlap = rvalue._overlap;
            _key_to_string = rvalue._key_to_string;

            return *this;
        };

        TValue& operator=(T_KEY& key) throw()
        {
            try {
                return Value(key);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"operator=", e);
            }
        }

        TValue operator=(T_KEY& key) const throw()
        {
            try {
                return Value(key);
            } catch (CException& e) {
                NATIVE_THROW_CAUGHT(L"operator=", e);
            }
        }
    };
}}

#endif